﻿
import os
import pandas as pd
import numpy as np
import torch
import torch.nn as nn
import torch.utils.data as Data


# Hyper-parameters
# in_channels = 3
# out_channels = 64
# sequence_length = 66
# input_size = 3
# hidden_size = 32
# num_layers = 2
# num_classes = 2
# batch_size = 64
# num_epochs = 5
# learning_rate = 0.001
# bidirectional= True

# Device configuration
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device,'能用')

class CNN_LSTM(nn.Module):
    def __init__(self, in_channels, out_channels, input_size, hidden_size, num_classes, num_layers, bidirectional):
        super(CNN_LSTM, self).__init__()
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.num_classes = num_classes
        self.num_layers = num_layers
        self.bidirectional = bidirectional

        ### (batch_size=30, seq_len=13, input_size=3) ---> permute(0, 2, 1)
        ### (30, 3, 13)
        self.conv = nn.Sequential(
            nn.Conv1d(in_channels, out_channels, kernel_size=3),
            nn.ReLU(),
            nn.MaxPool1d(kernel_size=3, stride=1)
        )
        self.fc_cnn = nn.Linear(out_channels*(13-4), num_classes)


        self.lstm = nn.LSTM(input_size=self.input_size, hidden_size=self.hidden_size, batch_first=True,
                            num_layers=self.num_layers, bidirectional=self.bidirectional)
        if self.bidirectional:
            self.fc_lstm = nn.Linear(hidden_size * 2, num_classes)
        else:
            self.fc_lstm = nn.Linear(hidden_size, num_classes)


        self.fc_com = nn.Linear(3*3, num_classes)

        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        x1 = x
        # x1 = x[:,0:40,:]
        # x2 = x[:,40:53,:]
        # x3 = x[:,53:66,:]

        # 初始化一个h0,也即c0，在RNN中一个Cell输出的ht和Ct是相同的，而LSTM的一个cell输出的ht和Ct是不同的
        # 维度[layers, batch, hidden_len]
        if self.bidirectional:
            h0 = torch.zeros(self.num_layers * 2, x1.size(0), self.hidden_size).to(device)
            c0 = torch.zeros(self.num_layers * 2, x1.size(0), self.hidden_size).to(device)
        else:
            h0 = torch.zeros(self.num_layers, x1.size(0), self.hidden_size).to(device)
            c0 = torch.zeros(self.num_layers, x1.size(0), self.hidden_size).to(device)
        out1, (_, _) = self.lstm(x1, (h0, c0))
        output1 = self.fc_lstm(out1[:, -1, :]).squeeze(0)  # 因为有max_seq_len个时态，所以取最后一个时态即-1层
        predictions = self.sigmoid(output1)

        # x2 = x2.permute(0, 2, 1)
        # out2 = self.conv(x2)
        # out2 = out2.view(x2.size(0), out_channels*(13-4))
        # output2 = self.fc_cnn(out2).squeeze(0)
        #
        # x3 = x3.permute(0, 2, 1)
        # out3 = self.conv(x3)
        # out3 = out3.view(x3.size(0), out_channels*(13-4))
        # output3 = self.fc_cnn(out3).squeeze(0)
        # # #
        # out_com = torch.cat((out2, out3), 1)
        # output_com = self.fc_com(out_com).squeeze(0)

        return output1




class series_odds_prediction():
    def split_odds(self, str):
        try:
            item = str.split('|')
        except:
            print(str)
        home = item[0]
        draw = item[1]
        away = item[2]
        return home, draw, away

    def get_series_odds(self, df):
        home, draw, away = zip(*df.apply(lambda str: self.split_odds(str)))
        home = np.flipud(np.array(home, dtype=float))
        draw = np.flipud(np.array(draw, dtype=float))
        away = np.flipud(np.array(away, dtype=float))
        odds = np.stack((home, draw, away), axis=0)

        return odds

    def odds_to_probability(self, odds):
        QI = 1 / odds[:, 0] + 1 / odds[:, 1] + 1 / odds[:, 2]
        return_precentage = 1 / QI
        # probility_company = (return_precentage.unsqueeze(1) / odds).round(3)
        probility_company = torch.round((return_precentage.unsqueeze(1) / odds), decimals=3)

        return probility_company


    def load_data(self, loadfile=r'./prediction/datasets/today_matchs_time_series_odds.csv'):
        matchs = pd.read_csv(loadfile, encoding="gbk",header=None, names = ['league', 'season', 'bs_num_id', 'company_id', 'bs_time', 'hometeam', 'awayteam', 'FTR',
                        'time1', 'time2', 'time3', 'time4', 'time5', 'time6', 'time7', 'time8','time9', 'time10',
                        'time11', 'time12', 'time13', 'time14', 'time15', 'time16', 'time17', 'time18', 'time19','time20',
                        'time21', 'time22', 'time23', 'time24', 'time25', 'time26', 'time27', 'time28', 'time29','time30',
                        'time31', 'time32', 'time33', 'time34', 'time35', 'time36', 'time37', 'time38', 'time39','time40'])
        matchs.sort_values(by='bs_time', axis=0, ascending=True, inplace=True)
        matchs = matchs.reset_index(drop=True)
        matchs_info = matchs[['league', 'bs_time', 'hometeam', 'awayteam']]
        series_odds = matchs.filter(regex='^(time)')
        odds = series_odds.apply(lambda row: self.get_series_odds(row), axis=1)
        if len(odds) == 1:
            datas = odds[0]
            datas = datas[:, :, None]
        else:
            datas = odds[0]
            for i in range(1, len(odds)):
                datas = np.dstack((datas, odds[i]))
        datas = np.transpose(datas, [2, 1, 0])

        return datas, matchs_info

    def predict_3class(self, odds, matchs_info):
        # Hyper-parameters
        in_channels = 3
        out_channels = 64
        input_size = 3
        hidden_size = 96
        num_layers = 2
        num_classes = 3
        batch_size = 32
        bidirectional = True

        matchs_odds = torch.from_numpy(odds).float()
        # Data loader
        pred_loader = torch.utils.data.DataLoader(dataset=matchs_odds, batch_size=batch_size, shuffle=False)

        model = CNN_LSTM(in_channels, out_channels, input_size, hidden_size, num_classes, num_layers, bidirectional)
        # model = CNN_LSTM()
        model.load_state_dict(torch.load(r'./Data/model/model_3class_GPU(e4_h96).pt', map_location=torch.device('cpu')))
        model.to(device)
        model.eval()
        pred_result = pd.DataFrame(columns=['home_odd', 'draw_odd', 'away_odd', 'home', 'draw', 'away',
                                            'pred_home', 'pred_draw', 'pred_away', 'predict'])
        with torch.no_grad():
            for data in pred_loader:
                data = data.to(device)
                output = model(data)
                pred = output.argmax(dim=-1) # 找到概率最大的下标
                if len(pred.shape) == 0:
                    pred = pred.unsqueeze(0)
                pred = pred.view(len(pred), -1) ## [1,2]变换成[2,1]
                end_odd = (data[:, -1, :])  ##某一家的赔率
                probility_company = self.odds_to_probability(end_odd)
                # target = target.view_as(pred)
                if len(output.shape) == 1:
                    output.data = output.data.unsqueeze(0)

                probility = torch.nn.functional.softmax(output.data, dim=1)
                probility = torch.round(probility, decimals=3) ###保留3位小数
                probility = torch.cat((end_odd, probility_company, probility, pred), dim=1)
                probility = probility.cpu().numpy()
                probility = pd.DataFrame(probility, columns=['home_odd', 'draw_odd', 'away_odd', 'home', 'draw', 'away',
                                                             'pred_home', 'pred_draw', 'pred_away', 'predict'])
                pred_result = pd.concat([pred_result, probility], axis=0, ignore_index=True)
        pred_result = pd.concat([matchs_info, pred_result], axis=1)

        conditions1 = [(pred_result['predict'] == 0),
                      (pred_result['predict'] == 1),
                      (pred_result['predict'] == 2)]
        values = ['主胜', '平局', '客胜']
        pred_result['pred'] = np.select(conditions1, values)

        condition = [(pred_result.pred == "主胜") & (pred_result.home <= (pred_result.pred_home + 0.05)),
                     ((pred_result.pred == "客胜") & (pred_result.away <= pred_result.pred_away - 0.03)),
                     (pred_result.home >= (pred_result.pred_home + 0.09)),
                     (pred_result.away >= (pred_result.pred_away + 0.09))]

        values = ['价投-主', '价投-客', '价投-客', '价投-主']
        pred_result['valueBet'] = np.select(condition, values)
        pred_result = pred_result.drop(['predict'], axis=1)

        return pred_result


    ##### 客不败模试，只关注客队　######
    def predict_2class_H_NH(self, odds, matchs_info):
        in_channels = 3
        out_channels = 64
        input_size = 3
        hidden_size = 96
        num_layers = 2
        num_classes = 2
        batch_size = 32
        bidirectional = True

        matchs_odds = torch.from_numpy(odds).float()
        # Data loader
        pred_loader = torch.utils.data.DataLoader(dataset=matchs_odds, batch_size=batch_size, shuffle=False)

        # device = torch.device("cuda")
        model = CNN_LSTM(in_channels, out_channels, input_size, hidden_size, num_classes, num_layers, bidirectional)
        model.load_state_dict(torch.load(r'./Data/model/model_2class_GPU(H,NH)(e2_h96).pt', map_location=torch.device('cpu')))
        model.to(device)
        model.eval()
        pred_result = pd.DataFrame(columns=['home_odd', 'draw_odd', 'away_odd', 'home', 'draw', 'away',
                                            'pred_home', 'pred_not_home', 'predict'])
        with torch.no_grad():
            for data in pred_loader:
                data = data.to(device)
                output = model(data)
                # pred = output.argmax(dim=-1) # 找到概率最大的下标
                # pred = pred.view(len(pred), -1) ## 变换成二维
                # end_odd = (data[:, -1, :])  ##某一家的赔率
                # probility_company = self.odds_to_probability(end_odd)

                pred = output.argmax(dim=-1) # 找到概率最大的下标
                if len(pred.shape) == 0:
                    pred = pred.unsqueeze(0)
                pred = pred.view(len(pred), -1) ## [1,2]变换成[2,1]
                end_odd = (data[:, -1, :])  ##某一家的赔率
                probility_company = self.odds_to_probability(end_odd)
                # target = target.view_as(pred)
                if len(output.shape) == 1:
                    output.data = output.data.unsqueeze(0)

                probility = torch.nn.functional.softmax(output.data, dim=1)
                probility = torch.round(probility, decimals=3) ###保留3位小数
                probility = torch.cat((end_odd, probility_company, probility, pred), dim=1)
                probility = probility.cpu().numpy()
                probility = pd.DataFrame(probility, columns=['home_odd', 'draw_odd', 'away_odd', 'home', 'draw', 'away',
                                                                'pred_home', 'pred_not_home', 'predict'])
                pred_result = pd.concat([pred_result, probility], axis=0, ignore_index=True)
        pred_result = pd.concat([matchs_info, pred_result], axis=1)

        conditions = [(pred_result['predict'] == 0),
                      (pred_result['predict'] == 1)]
        values = ['主胜', '客不败']
        pred_result['pred_H_NH'] = np.select(conditions, values)
        pred_result = pred_result.drop(['predict'], axis=1)
        # condition = ((pred_result.pred == "客不败") & (0.5 < pred_result.pred_not_home) & (pred_result.pred_not_home < 0.56 ))
        # pred_result['valueBet'] = np.where(condition, '价值投注', '')

        return pred_result

    ##### 主不败模试，只关注主队　###########
    def predict_2class_NA_A(self, odds, matchs_info):
        in_channels = 3
        out_channels = 64
        input_size = 3
        hidden_size = 96
        num_layers = 2
        num_classes = 2
        batch_size = 32
        bidirectional = True

        matchs_odds = torch.from_numpy(odds).float()
        # Data loader
        pred_loader = torch.utils.data.DataLoader(dataset=matchs_odds, batch_size=batch_size, shuffle=False)
        # device = torch.device("cuda")
        model = CNN_LSTM(in_channels, out_channels, input_size, hidden_size, num_classes, num_layers, bidirectional)
        model.load_state_dict(torch.load(r'./Data/model/model_2class_GPU(NA,A)(e3_h96).pt', map_location=torch.device('cpu')))
        model.to(device)
        model.eval()
        pred_result = pd.DataFrame(columns=['home_odd', 'draw_odd', 'away_odd', 'home', 'draw', 'away',
                                            'pred_not_away', 'pred_away', 'predict'])
        with torch.no_grad():
            for data in pred_loader:
                data = data.to(device)
                output = model(data)
                # pred = output.max(1, keepdim=True)[1]  # 找到概率最大的下标
                # end_odd = (data[:, -1, :])  ##某一家的赔率
                # probility_company = self.odds_to_probability(end_odd)

                pred = output.argmax(dim=-1) # 找到概率最大的下标
                if len(pred.shape) == 0:
                    pred = pred.unsqueeze(0)
                pred = pred.view(len(pred), -1) ## [1,2]变换成[2,1]
                end_odd = (data[:, -1, :])  ##某一家的赔率
                probility_company = self.odds_to_probability(end_odd)
                # target = target.view_as(pred)
                if len(output.shape) == 1:
                    output.data = output.data.unsqueeze(0)

                probility = torch.nn.functional.softmax(output.data, dim=1)
                probility = torch.round(probility, decimals=3) ###保留3位小数
                probility = torch.cat((end_odd, probility_company, probility, pred), dim=1)
                probility = probility.cpu().numpy()
                probility = pd.DataFrame(probility, columns=['home_odd', 'draw_odd', 'away_odd', 'home', 'draw', 'away',
                                                                'pred_not_away', 'pred_away', 'predict'])
                # probility = probility.round({'home_odd':3, 'draw_odd':3, 'away_odd':3, 'home':3, 'draw':3, 'away':3, 'pred_not_away':3, 'pred_away':3})
                pred_result = pd.concat([pred_result, probility], axis=0, ignore_index=True)
        pred_result = pd.concat([matchs_info, pred_result], axis=1)

        conditions = [(pred_result['predict'] == 0),
                      (pred_result['predict'] == 1)]
        values = ['主不败', '客胜']
        pred_result['pred_NA_A'] = np.select(conditions, values)
        pred_result = pred_result.drop(['predict'], axis=1)

        # condition = ((pred_result.pred == "主不败") & (0.5 < pred_result.pred_not_away) & (pred_result.pred_not_away < 0.60))
        # pred_result['valueBet'] = np.where(condition, '价值投注', '')

        return pred_result



def save_result(pred_result, file):
    """
    把预测结果保存到文件
    :param pred_result: 预测结果(dataframe)
    :param file: 文件名称
    :return:
    """
    if (os.path.exists(file)):
        matchs = pd.read_csv(file, encoding="utf-8")
        for i in range(len(pred_result)):
            bs_num_id = pred_result.iloc[i]["bs_num_id"]
            ids = matchs.loc[:, "bs_num_id"]
            is_exists = False
            for j in range(len(ids)-1, -1, -1):
                if bs_num_id == ids.iloc[j]:
                    df1 = matchs[:(j+1)]
                    df2 = matchs[(j+1):]
                    df3 = pred_result[i:i+1]
                    matchs = pd.concat([df1, df3, df2], axis=0)
                    is_exists = True
                    break
            if not is_exists:
                matchs = pd.concat([matchs, pd.DataFrame(pred_result.iloc[i]).T], axis=0, ignore_index=True)
        matchs.to_csv(file, encoding="utf-8", index=None)
    else:
        pred_result.to_csv(file, encoding="utf-8", index=None)


def lstm_series_odds_predict():
    predition = series_odds_prediction()
    matchs, matchs_info = predition.load_data()
    pred_result_3class = predition.predict_3class(matchs, matchs_info)
    pred_result_NA = predition.predict_2class_NA_A(matchs, matchs_info)
    pred_result_NH = predition.predict_2class_H_NH(matchs, matchs_info)

    pred_result_2class = pd.concat([pred_result_NA, pred_result_NH[['pred_home','pred_not_home', 'pred_H_NH']]], axis=1)
    condition = [(0.6 < pred_result_2class.pred_not_away) & (0.6 < pred_result_2class.pred_home),
                   (0.6 < pred_result_2class.pred_away) & (0.6 < pred_result_2class.pred_not_home)]
    values = ['价投-主', '价投-客']
    pred_result_2class['valueBet'] = np.select(condition, values)


    # nowtime = time.strftime('%Y%m%d', time.localtime())
    result_file_2class = "./prediction/datasets/lstm_odds_predResult_2_class.csv"
    result_file_3class = "./prediction/datasets/lstm_odds_predResult_3_class.csv"
    # pred_result_2class.to_csv(result_file_2class, encoding="gbk", index=None)
    # pred_result_3class.to_csv(result_file_3class, encoding="gbk", index=None)

    # save_result(pred_result_2class, result_file_2class)
    save_result(pred_result_3class, result_file_3class)





